// LAB 6: Your driver code here
#include <kern/pci.h>
#include <kern/e100.h>
#include <inc/stdio.h>
#include <inc/x86.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/env.h>
#include <kern/pmap.h>
#include <kern/env.h>
#include <kern/trap.h>
#include <kern/picirq.h>
#define MAX_ETHERNET_PAC 1518
#define CB_STATUS_C 0x8000
#define CB_STATUS_OK 0x2000

#define CU_IDLE 0x0
#define CU_SUSPEND 0x1

#define CU_START (0x1<<4)
#define CU_RESUME (0x2<<4)

#define RU_START 0x1
#define RU_RESUME 0x2
#define RU_NOP 0x0
#define RU_SUSPEND 0x4

#define RU_C 0x8000
#define RU_OK 0x2000
#define RU_S 0x4000
#define CU_SIZE 10
#define RU_SIZE 10
static void delay(void);
struct tcb
{
    uint32_t addr;
    uint16_t cnt;
    uint8_t thrs;
    uint8_t num;
    uint8_t data[MAX_ETHERNET_PAC];
};
struct cb
{
    volatile uint16_t status;
    uint16_t cmd;
    uint32_t link;
    struct tcb tbd;
    struct cb* next;
};
struct rfd
{
    volatile uint16_t status;
    uint16_t cmd;
    uint32_t link;
    uint32_t reserved;
    uint16_t cnt;
    uint16_t size;
    uint8_t data[MAX_ETHERNET_PAC];
    struct rfd* next;
};
struct cb cu_ring[CU_SIZE];
struct rfd ru_ring[RU_SIZE];
unsigned int iobase;
bool free = 1;
unsigned int head = 0;
unsigned int rhead = 0;
int avail = RU_SIZE;
int wait = 0;
int cavail = CU_SIZE;
int cwait = 0;
struct cb * cbhead;
struct rfd * rfdhead;
static void e100_exec_cmd(int csr_comp, uint8_t cmd);
static void cb_init();
static void rfd_init();
int pci_device_attach(struct pci_func * pcif)
{
    pci_func_enable(pcif);
    //reset
    iobase = pcif->reg_base[1];
    
	//software reset
    outl(iobase + 8, 0);
    
    //must wait 10us
    delay();

    outb(iobase + 3, 1);
    delay();
    //reset cu_ring
    int i;
   	memset(cu_ring,0, sizeof(cu_ring));
   	for(i = 0; i < CU_SIZE; i++)
   	{
   		cu_ring[i].link = PADDR(&cu_ring[(i+1)%CU_SIZE]);
   	}

    //reset ru_ring
   // rfd_init();
    memset(ru_ring, 0, sizeof(ru_ring));
    for(i = 0; i < RU_SIZE; i++)
    {
        ru_ring[i].link = PADDR(&ru_ring[(i + 1)%RU_SIZE]);
        ru_ring[i].size = MAX_ETHERNET_PAC;
    }
    //set addr
    ru_ring[RU_SIZE-1].cmd |= RU_S;
    outl(iobase + 4, PADDR(&ru_ring[0]));
    outw(iobase + 2, RUC_START); 
    return 0;
}
static void delay(void)
{
    inb(0x84);
	inb(0x84);
	inb(0x84);
	inb(0x84);
    inb(0x84);
	inb(0x84);
	inb(0x84);
	inb(0x84);
}

int e100_send(void * buf, int len)
{
    int i;
    uint16_t status;
    
    if(cwait > 0)
    {
      search:
    for(i = head; i < CU_SIZE + head; i++)
    {
        
        if(cu_ring[i%CU_SIZE].status & CB_STATUS_C)
        {
            cavail++;
            cwait--;
        }
    }
		if(cavail == 0)
			goto search;
    }
    memmove(cu_ring[head].tbd.data, buf, len);
    cu_ring[(head+CU_SIZE-1)%CU_SIZE].cmd &= ~CBF_S;
    cu_ring[head].tbd.cnt = len;
    cu_ring[head].tbd.thrs = 0xe0;
    cu_ring[head].tbd.addr = 0xffffffff;
    cu_ring[head].cmd = CBF_S | CBC_TRANSMIT;
    cu_ring[head].status = 0;
    
    int scb_status = inb(iobase + CSR_STATUS);
    if ((scb_status & CUS_MASK) == CUS_SUSPENDED)
        e100_exec_cmd (CSR_COMMAND, CUC_RESUME); 
	else
	{
        outl(iobase + 0x4, PADDR(&cu_ring[0]));
         e100_exec_cmd (CSR_COMMAND, CUC_START); 
    }
    head = (head+1) % CU_SIZE;
    return 0;
}


int 
e100_recv (void *data)
{
	//cprintf("re\n");
    int i = rhead;
    while (avail > 0 && (ru_ring[(i++)%RU_SIZE].status & RFDS_C) != 0) {
       avail --;
       wait ++;
    }    
	//cprintf("re\n");
    if (wait == 0)
        return -1;
    avail++;
    wait--;
    
    ru_ring[(rhead + RU_SIZE - 1)%RU_SIZE].cmd &= ~RFDF_S;
    ru_ring[rhead].status = 0;
    ru_ring[rhead].cmd = RU_S;
    
    int r = ru_ring[rhead].cnt & RFD_AC_MASK;
    memmove (data, ru_ring[rhead].data, r);

    rhead = (rhead+1)%RU_SIZE;
	//cprintf("re\n");
    
    int scb_status = inb(iobase + CSR_STATUS);
    cprintf("%x\n",scb_status);
    if ((scb_status & RUS_MASK) == RUS_SUSPEND)
        e100_exec_cmd(CSR_COMMAND, RUC_RESUME); 

    return r;
}

static void
e100_exec_cmd (int csr_comp, uint8_t cmd)
{
    int scb_command;

    outb(iobase + csr_comp, cmd);
    do {
        scb_command = inb(iobase + CSR_COMMAND);
    } while (scb_command != 0);
}


